package com.gitee.security.filter;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.gitee.bo.UserDetailsBo;
import com.gitee.config.JWTConfig;
import com.gitee.contant.Constants;
import com.gitee.service.impl.UserDetailsServiceImpl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

/**
 * @description: 自定义JWT认证过滤器
 * 该类继承自BasicAuthenticationFilter，在doFilterInternal方法中，
 * 从http头的Authorization 项读取token数据，然后用Jwts包提供的方法校验token的合法性。
 * 如果校验通过，就认为这是一个取得授权的合法请求
 * @author: chennl
 * @date: 2021/12/28
 * @version: 1.0
 */
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

    private final JWTConfig jwtConfig;
    private final UserDetailsServiceImpl userDetailsService;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTConfig jwtConfig, UserDetailsServiceImpl userDetailsService) {
        super(authenticationManager);
        this.jwtConfig = jwtConfig;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String token = request.getHeader(jwtConfig.getTokenHeader());
        if (StringUtils.isBlank(token) || !token.startsWith(jwtConfig.getTokenPrefix())) {
            chain.doFilter(request, response);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = buildAuthentication(token, response);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken buildAuthentication(String token, HttpServletResponse response) {
        // token去掉前缀部分
        token = token.substring(jwtConfig.getTokenPrefix().length());
        Claims claims = Jwts.parser().setSigningKey(jwtConfig.getSecret()).parseClaimsJws(token).getBody();
        // token签发时间
        long issuedAt = claims.getIssuedAt().getTime();
        // 当前时间
        long currentTimeMillis = System.currentTimeMillis();
        // token过期时间
        long expirationTime = claims.getExpiration().getTime();
        // 1. 签发时间 < 当前时间 < (签发时间+((token过期时间-token签发时间)/2)) 不刷新token
        // 2. (签发时间+((token过期时间-token签发时间)/2)) < 当前时间 < token过期时间 刷新token并返回给前端
        // 3. token过期时间 < 当前时间 跳转登录，重新登录获取token
        // 验证token时间有效性
        if ((issuedAt + ((expirationTime - issuedAt) / 2)) < currentTimeMillis && currentTimeMillis < expirationTime) {
            // 重新生成token start
            String refreshToken = Jwts.builder()
                    .setClaims(claims)
                    .setIssuedAt(new Date())//签发时间
                    .setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration()))//过期时间
                    .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret()) //JWT签名算法
                    .compact();
            // 重新生成token end

            /*// 主动刷新token，并返回给前端
            response.addHeader(jwtConfig.getRefreshTokenHeader(), refreshToken);*/
            response.addCookie(new Cookie(jwtConfig.getTokenHeader(), jwtConfig.getTokenPrefix() + refreshToken));
        }

        String userName = (String) claims.get(Constants.CLAIM_KEY_USERNAME);
        if (StringUtils.isNotBlank(userName)) {
            UserDetailsBo userDetails = userDetailsService.loadUserByUsername(userName);
            return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        }
        return null;
    }
}
